﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;

namespace Trogsoft.Weather.SDK
{

    public enum WeatherVisibility {
        Unknown,
        VeryPoor,
        Poor,
        Moderate,
        Good,
        VeryGood,
        Excellent
    }

    public enum PredominantWeather {
        ClearNight = 0,
        SunnyDay = 1,
        PartlyCloudyNight = 2,
        PartlyCloudyDay = 3,
        Mist = 5,
        Fog = 6,
        Cloudy = 7,
        Overcast = 8,
        LightRainShowerNight = 9,
        LightRainShowerDay = 10,
        Drizzle = 11,
        LightRain = 12,
        HeavyRainShowerNight = 13,
        HeavyRainShowerDay = 14,
        HeavyRain = 15,
        SleetShowerNight = 16,
        SleetShowerDay = 17,
        Sleet = 18,
        HailShowerNight = 19,
        HailShowerDay = 20,
        Hail = 21,
        LightSnowShowerNight = 21,
        LightSnowShowerDay = 22,
        LightSnow = 23,
        HeavySnowShowerNight = 24,
        HeavySnowShowerDay = 25,
        HeavySnow = 26,
        ThunderShowerNight = 27,
        ThunderShowerDay = 28,
        Thunder = 29
    }

    public class ForecastData {
        public DateTime Day { get; private set; }
        public double WindSpeed { get; private set; }
        public double Temperature { get; private set; }
        public double FeelsLike { get; private set; }
        public string WindDirection { get; private set; }
        public WeatherVisibility Visibility { get; private set; }
        public PredominantWeather WeatherType { get; private set; }
        public int PrecipitationChance { get; private set; }
        public int Humidity { get; private set; }
        public double WindGustSpeed { get; private set; }
        public string Period { get; private set; }
        internal ForecastData(XmlNode xn)
        {
            this.Day = DateTime.Parse(xn.Attributes["date"].Value);
            this.WindSpeed = Convert.ToDouble(xn.Attributes["windspeed"].Value);
            this.Temperature = Convert.ToDouble(xn.Attributes["temperature"].ValueOr("0"));
            this.FeelsLike = Convert.ToDouble(xn.Attributes["feelslike"].ValueOr("0"));
            this.WindDirection = xn.Attributes["winddirection"].Value;
            this.Visibility = CodeToVis(xn.Attributes["visibility"].ValueOr("UN"));
            this.WeatherType = (PredominantWeather)Convert.ToInt32(xn.Attributes["weather"].Value);
            this.PrecipitationChance = Convert.ToInt32(xn.Attributes["precipitation"].Value);
            this.Humidity = Convert.ToInt32(xn.Attributes["humidity"].Value);
            this.WindGustSpeed = Convert.ToDouble(xn.Attributes["windgust"].Value);
            this.Period = xn.Attributes["period"].Value;
        }   
        private WeatherVisibility CodeToVis(string code)
        {
            if (code == "UN") { return WeatherVisibility.Unknown; }
            if (code == "EX") { return WeatherVisibility.Excellent; }
            if (code == "GO") { return WeatherVisibility.Good; }
            if (code == "MO") { return WeatherVisibility.Moderate; }
            if (code == "PO") { return WeatherVisibility.Poor; }
            if (code == "VG") { return WeatherVisibility.VeryGood; }
            if (code == "VP") { return WeatherVisibility.VeryPoor; }
            return WeatherVisibility.Unknown;
        }
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (PropertyInfo pi in this.GetType().GetProperties())
            {
                sb.Append(pi.Name + ": " + pi.GetValue(this, null) + "\n");
            }
            return sb.ToString();
        }
    }

    public class Forecast {
        public ForecastData Day { get; internal set; }
        public ForecastData Night { get; internal set; }
        public override string ToString()
        {
            return "Day: " + Day.ToString() + "\nNight: " + Night.ToString();
        }
    }

    public class ForecastList : List<Forecast> { }

    public class Observations
    {
        public string WindDirection { get; private set; }
        public int Pressure { get; private set; }
        public double WindSpeed { get; private set; }
        public double Temperature { get; private set; }
        public int Visibility { get; private set; }
        public PredominantWeather Weather { get; private set; }
        public DateTime Time { get; private set; }
        internal static Observations Construct(XmlNode xn)
        {
            Observations o = new Observations();
            o.WindDirection = xn.Attributes["winddirection"].Value;
            o.Pressure = Convert.ToInt32(xn.Attributes["pressure"].ValueOr("0"));
            o.WindSpeed = Convert.ToDouble(xn.Attributes["windspeed"].ValueOr("0"));
            o.Temperature = Convert.ToDouble(xn.Attributes["temperature"].ValueOr("0"));
            o.Visibility = Convert.ToInt32(xn.Attributes["visibility"].ValueOr("0"));
            o.Weather = (PredominantWeather)Convert.ToInt32(xn.Attributes["weather"].Value);
            o.Time = DateTime.Parse(xn.Attributes["date"].Value);
            return o;
        }
    }

    public class ForecastContainer
    {
        public WeatherLocation Location { get; private set; }
        public ForecastList Forecasts { get; private set; }
        internal ForecastContainer(WeatherLocation location)
        {
            Location = location;
            Forecasts = new ForecastList();
        }
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(this.Location.ToString());
            foreach (Forecast f in Forecasts)
            {
                sb.Append(f.ToString());
            }
            return sb.ToString();
        }
    }

    public class ObservationList : List<Observations> {
    }

    public class ObservationContainer {
        public WeatherLocation Location { get; internal set; }
        public ObservationList Observations { get; internal set; }
        public ObservationContainer() {
            Observations = new ObservationList();
        }
    }

    public class WeatherLocation
    {
        public string Name { get; private set; }
        public double Latitude { get; private set; }
        public double Longitude { get; private set; }
        internal WeatherLocation() { }
        internal static WeatherLocation Construct(XmlNode xn) {
            WeatherLocation wl = new WeatherLocation();
            wl.Name = xn.Attributes["name"].Value;
            wl.Latitude = Convert.ToDouble(xn.Attributes["lat"].Value);
            wl.Longitude = Convert.ToDouble(xn.Attributes["long"].Value);
            return wl;
        }
        public override string ToString()
        {
            return "Location: " + this.Name + "; Coordinates=(" + Latitude.ToString() + "," + Longitude.ToString() + ")";
        }
    }

    public class WeatherAPI
    {

        private string apiKey;

        public WeatherAPI(string APIKey)
        {
            apiKey = APIKey;
        }

        private string ConstructUrl(params string[] pairs)
        {
            string url = "http://api.trogsoft.net/weather/query.wq?key=" + apiKey;
            foreach (string s in pairs)
            {
                url += "&" + s;
            }
            return url;
        }

        public int AllowedRequestsPerDay
        {
            get
            {
                XmlDocument xd = new XmlDocument();
                xd.Load(ConstructUrl(new string[] { "action=Status" }));
                return Convert.ToInt32(xd.SelectSingleNode("//usage").Attributes["limit"].Value);
            }
        }

        public int UsedRequests
        {
            get
            {
                XmlDocument xd = new XmlDocument();
                xd.Load(ConstructUrl(new string[] { "action=Status" }));
                return Convert.ToInt32(xd.SelectSingleNode("//usage").Attributes["used"].Value);
            }
        }

        public IEnumerable<string> GetLocations(string Input)
        {
            XmlDocument xd = new XmlDocument();
            xd.Load(ConstructUrl(new string[] { "action=CheckLocation", "name=" + Input  }));
            foreach (XmlNode xn in xd.SelectNodes("//location"))
            {
                yield return xn.Attributes["name"].Value;
            }
        }

        public ForecastContainer GetForecast(double Lat, double Long)
        {
            XmlDocument xd = new XmlDocument();
            xd.Load(ConstructUrl(new string[] { "action=GetForecast", "lat="+Lat.ToString(), "long="+Long.ToString() }));
            WeatherLocation wl = WeatherLocation.Construct(xd.SelectSingleNode("//observation"));
            ForecastContainer fc = new ForecastContainer(wl);
            List<ForecastData> fcd = new List<ForecastData>();

            Forecast f = new Forecast();
            foreach (XmlNode xn in xd.SelectNodes("//forecast"))
            {
                ForecastData fd = new ForecastData(xn);
                if (fd.Period == "Day") { f = new Forecast(); f.Day = fd; }
                if (fd.Period == "Night") { f.Night = fd; fc.Forecasts.Add(f); }   
            }

            return fc;
        }

        public ForecastContainer GetForecast(string LocationName)
        {
            XmlDocument xd = new XmlDocument();
            xd.Load(ConstructUrl(new string[] { "action=GetForecastByLocationName", "name=" + LocationName }));
            WeatherLocation wl = WeatherLocation.Construct(xd.SelectSingleNode("//observation"));
            ForecastContainer fc = new ForecastContainer(wl);

            Forecast f = new Forecast();
            foreach (XmlNode xn in xd.SelectNodes("//forecast"))
            {
                ForecastData fd = new ForecastData(xn);
                if (fd.Period == "Day") { f = new Forecast(); f.Day = fd; }
                if (fd.Period == "Night") { f.Night = fd; fc.Forecasts.Add(f); }
            }

            return fc;
        }

        public ObservationContainer GetObservations(string LocationName)
        {

            XmlDocument xd = new XmlDocument();
            xd.Load(ConstructUrl(new string[] { "action=GetCurrentObservationsByLocationName", "name=" + LocationName }));
            WeatherLocation wl = WeatherLocation.Construct(xd.SelectSingleNode("//observation"));
            ObservationContainer oc = new ObservationContainer();
            oc.Location = wl;

            foreach (XmlNode xn in xd.SelectNodes("//observations"))
            {
                oc.Observations.Add(Observations.Construct(xn));                
            }

            return oc;

        }

        public ObservationContainer GetObservations(string LocationName, int DaysHistory)
        {

            XmlDocument xd = new XmlDocument();
            xd.Load(ConstructUrl(new string[] { "action=GetCurrentObservationsByLocationName", "name=" + LocationName, "days="+DaysHistory.ToString() }));
            WeatherLocation wl = WeatherLocation.Construct(xd.SelectSingleNode("//observation"));
            ObservationContainer oc = new ObservationContainer();
            oc.Location = wl;

            foreach (XmlNode xn in xd.SelectNodes("//observations"))
            {
                oc.Observations.Add(Observations.Construct(xn));
            }

            return oc;

        }

        public ObservationContainer GetObservations(string LocationName, DateTime StartDate, DateTime EndDate)
        {

            XmlDocument xd = new XmlDocument();
            xd.Load(ConstructUrl(new string[] { "action=GetCurrentObservationsByLocationName", "name=" + LocationName, "start=" + StartDate.ToString("yyyy-MM-dd"), "end=" + EndDate.ToString("yyyy-MM-dd") }));
            WeatherLocation wl = WeatherLocation.Construct(xd.SelectSingleNode("//observation"));
            ObservationContainer oc = new ObservationContainer();
            oc.Location = wl;

            foreach (XmlNode xn in xd.SelectNodes("//observations"))
            {
                oc.Observations.Add(Observations.Construct(xn));
            }

            return oc;

        }

        public ObservationContainer GetObservations(double Latitude, double Longitude)
        {

            XmlDocument xd = new XmlDocument();
            xd.Load(ConstructUrl(new string[] { "action=GetCurrentObservations", "lat=" + Latitude.ToString(), "long=" + Longitude.ToString() }));
            WeatherLocation wl = WeatherLocation.Construct(xd.SelectSingleNode("//observation"));
            ObservationContainer oc = new ObservationContainer();
            oc.Location = wl;

            foreach (XmlNode xn in xd.SelectNodes("//observations"))
            {
                oc.Observations.Add(Observations.Construct(xn));
            }

            return oc;

        }

        public ObservationContainer GetObservations(double Latitude, double Longitude, int DaysHistory)
        {

            XmlDocument xd = new XmlDocument();
            xd.Load(ConstructUrl(new string[] { "action=GetCurrentObservations", "lat=" + Latitude.ToString(), "long=" + Longitude.ToString(), "days="+DaysHistory.ToString() }));
            WeatherLocation wl = WeatherLocation.Construct(xd.SelectSingleNode("//observation"));
            ObservationContainer oc = new ObservationContainer();
            oc.Location = wl;

            foreach (XmlNode xn in xd.SelectNodes("//observations"))
            {
                oc.Observations.Add(Observations.Construct(xn));
            }

            return oc;

        }

        public ObservationContainer GetObservations(double Latitude, double Longitude, DateTime StartDate, DateTime EndDate)
        {

            XmlDocument xd = new XmlDocument();
            xd.Load(ConstructUrl(new string[] { "action=GetCurrentObservations", "lat=" + Latitude.ToString(), "long=" + Longitude.ToString(), "start=" + StartDate.ToString("yyyy-MM-dd"), "end="+EndDate.ToString("yyyy-MM-dd") }));
            WeatherLocation wl = WeatherLocation.Construct(xd.SelectSingleNode("//observation"));
            ObservationContainer oc = new ObservationContainer();
            oc.Location = wl;

            foreach (XmlNode xn in xd.SelectNodes("//observations"))
            {
                oc.Observations.Add(Observations.Construct(xn));
            }

            return oc;

        }

    }

    public static class WeatherExtension
    {
        public static string ValueOr(this XmlAttribute @this, string Default)
        {
            if (@this == null)
            {
                return Default;
            }
            if (@this.Value == "")
            {
                return Default;
            }
            return @this.Value;
        }

    }

}
